{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Inference for an image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"0\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from __future__ import print_function\n",
    "import argparse\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.utils.data\n",
    "from torch import nn, optim\n",
    "from torch.nn import functional as F\n",
    "from torchvision import datasets, transforms\n",
    "import torch.backends.cudnn as cudnn\n",
    "from torchvision.utils import save_image\n",
    "from net import MaskGenerator, ResiduePredictor\n",
    "from mydataset import MyDataset\n",
    "import cv2\n",
    "import os\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "#### User inputs\n",
    "\n",
    "run_name = 'sample'\n",
    "num_primary_color = 7 \n",
    "csv_path = 'sample.csv' # なんでも良い．後方でパスを置き換えるから\n",
    "resize_scale_factor = 1  \n",
    "\n",
    "# image name and palette color values\n",
    "img_name = 'apple.jpg'; manual_color_0 = [253, 253, 254]; manual_color_1 = [203, 194, 170]; manual_color_2 = [83, 17, 22]; manual_color_3 = [205, 118, 4]; manual_color_4 = [220, 222, 11]; manual_color_5 = [155, 24, 10]; manual_color_6 = [171, 75, 67]; \n",
    "#img_name = 'boat.png'; manual_color_0 = [25, 21, 16]; manual_color_1 = [153, 155, 163]; manual_color_2 = [177,189,206]; manual_color_3 = [94, 89, 88]; manual_color_4 = [213, 215, 221]; manual_color_5 = [85,26,20]; manual_color_6 = [160,217,214]; \n",
    "#img_name = 'buildings.png'; manual_color_0 = [59, 66, 80]; manual_color_1 = [12, 12, 11]; manual_color_2 = [65, 56, 43]; manual_color_3 = [78, 92, 120]; manual_color_4 = [223, 192, 124]; manual_color_5 = [128, 102, 63]; manual_color_6 = [36, 36, 33]; \n",
    "#img_name = 'castle.jpg'; manual_color_0 = [60, 81, 116]; manual_color_1 = [175, 198, 215]; manual_color_2 = [0, 0, 0]; manual_color_3 = [114, 149, 185]; manual_color_4 = [142, 172, 198]; manual_color_5 = [92, 116, 149]; manual_color_6 = [226, 221, 222]; \n",
    "#img_name = 'girls.png'; manual_color_0 = [125, 116, 105]; manual_color_1 = [155, 162, 191]; manual_color_2 = [52, 60, 39]; manual_color_3 = [87, 120, 196]; manual_color_4 = [87, 107, 56]; manual_color_5 = [19, 26, 10]; manual_color_6 = [183,187,209]; \n",
    "#img_name = 'rowboat1.png'; manual_color_2 = [175, 77, 13]; manual_color_1 = [51, 45, 39]; manual_color_0 = [93, 89, 90]; manual_color_6 = [245, 141, 84]; manual_color_4 = [14, 13, 7]; manual_color_5 = [62, 71, 74]; manual_color_3 = [158,153,157]; \n",
    "#img_name = 'scrooge.png'; manual_color_0 = [254, 254, 254]; manual_color_1 = [78, 71, 65]; manual_color_2 = [211, 182, 135]; manual_color_3 = [165, 127, 100]; manual_color_4 = [40, 38, 34]; manual_color_5 = [112, 45, 31]; manual_color_6 = [177, 57, 35]; \n",
    "#img_name = 'turquoise.png'; manual_color_0 = [86, 59, 67]; manual_color_1 = [121, 132, 148]; manual_color_2 = [228, 186, 156]; manual_color_3 = [53, 35, 34]; manual_color_4 = [190, 135, 122]; manual_color_5 = [94, 152, 154]; manual_color_6 = [254,229,216]; \n",
    "#img_name = 'orange.png'; manual_color_0 = [79, 81, 59]; manual_color_1 = [112, 117, 105]; manual_color_2 = [137, 92, 41]; manual_color_3 = [201,214,197]; manual_color_4 = [42, 53, 49]; manual_color_5 = [168, 130, 40]; manual_color_6 = [114, 60, 31]; \n",
    "\n",
    "####\n",
    "\n",
    "img_path = '../dataset/test/' + img_name\n",
    "\n",
    "path_mask_generator = 'results/' + run_name + '/mask_generator.pth'\n",
    "path_residue_predictor = 'results/' + run_name + '/residue_predictor.pth'\n",
    "\n",
    "if num_primary_color == 7:\n",
    "    manual_colors = np.array([manual_color_0, manual_color_1, manual_color_2, manual_color_3,\\\n",
    "                                               manual_color_4, manual_color_5, manual_color_6]) /255\n",
    "elif num_primary_color == 6:\n",
    "    manual_colors = np.array([manual_color_0, manual_color_1, manual_color_2, manual_color_3,\\\n",
    "                                               manual_color_4, manual_color_5]) /255"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    os.makedirs('results/%s/%s' % (run_name, img_name))\n",
    "except OSError:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ResiduePredictor(\n",
       "  (conv1): Conv2d(31, 62, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "  (conv2): Conv2d(62, 124, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "  (conv3): Conv2d(124, 248, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)\n",
       "  (deconv1): ConvTranspose2d(248, 124, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), output_padding=(1, 1), bias=False)\n",
       "  (deconv2): ConvTranspose2d(248, 62, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), output_padding=(1, 1), bias=False)\n",
       "  (deconv3): ConvTranspose2d(124, 62, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), output_padding=(1, 1), bias=False)\n",
       "  (conv4): Conv2d(65, 31, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "  (conv5): Conv2d(31, 21, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))\n",
       "  (bn1): BatchNorm2d(62, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bn2): BatchNorm2d(124, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bn3): BatchNorm2d(248, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bnde1): BatchNorm2d(124, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bnde2): BatchNorm2d(62, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bnde3): BatchNorm2d(62, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       "  (bn4): BatchNorm2d(31, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)\n",
       ")"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_dataset = MyDataset(csv_path, num_primary_color, mode='test')\n",
    "test_loader = torch.utils.data.DataLoader(\n",
    "    test_dataset,\n",
    "    batch_size=1,\n",
    "    shuffle=False,\n",
    "    num_workers=0,\n",
    "    )\n",
    "\n",
    "\n",
    "\n",
    "device = 'cuda'\n",
    "\n",
    "# define model\n",
    "mask_generator = MaskGenerator(num_primary_color).to(device)\n",
    "residue_predictor = ResiduePredictor(num_primary_color).to(device)\n",
    "\n",
    "\n",
    "# load params\n",
    "mask_generator.load_state_dict(torch.load(path_mask_generator))\n",
    "residue_predictor.load_state_dict(torch.load(path_residue_predictor))\n",
    "\n",
    "\n",
    "# eval mode\n",
    "mask_generator.eval()\n",
    "residue_predictor.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 必要な関数を定義する\n",
    "\n",
    "def replace_color(primary_color_layers, manual_colors):\n",
    "    temp_primary_color_layers = primary_color_layers.clone()\n",
    "    for layer in range(len(manual_colors)):\n",
    "        for color in range(3):\n",
    "                temp_primary_color_layers[:,layer,color,:,:].fill_(manual_colors[layer][color])\n",
    "    return temp_primary_color_layers\n",
    "\n",
    "\n",
    "def cut_edge(target_img):\n",
    "    #print(target_img.size())\n",
    "    target_img = F.interpolate(target_img, scale_factor=resize_scale_factor, mode='area')\n",
    "    #print(target_img.size())\n",
    "    h = target_img.size(2)\n",
    "    w = target_img.size(3)\n",
    "    h = h - (h % 8)\n",
    "    w = w - (w % 8)\n",
    "    target_img = target_img[:,:,:h,:w]\n",
    "    #print(target_img.size())\n",
    "    return target_img\n",
    "\n",
    "def alpha_normalize(alpha_layers):\n",
    "    # constraint (sum = 1)\n",
    "    # layersの状態で受け取り，その形で返す. bn, ln, 1, h, w\n",
    "    return alpha_layers / (alpha_layers.sum(dim=1, keepdim=True) + 1e-8)\n",
    "\n",
    "def read_backimage():\n",
    "    img = cv2.imread('../dataset/backimage.jpg')\n",
    "    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n",
    "    img = img.transpose((2,0,1))\n",
    "    img = img/255\n",
    "    img = torch.from_numpy(img.astype(np.float32))\n",
    "\n",
    "    return img.view(1,3,256,256).to(device)\n",
    "\n",
    "backimage = read_backimage()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "from guided_filter_pytorch.guided_filter import GuidedFilter\n",
    "\n",
    "def proc_guidedfilter(alpha_layers, guide_img):\n",
    "    # guide_imgは， 1chのモノクロに変換\n",
    "    # target_imgを使う． bn, 3, h, w\n",
    "    guide_img = (guide_img[:, 0, :, :]*0.299 + guide_img[:, 1, :, :]*0.587 + guide_img[:, 2, :, :]*0.114).unsqueeze(1)\n",
    "        \n",
    "    # lnのそれぞれに対してguideg filterを実行\n",
    "    for i in range(alpha_layers.size(1)):\n",
    "        # layerは，bn, 1, h, w\n",
    "        layer = alpha_layers[:, i, :, :, :]\n",
    "        \n",
    "        processed_layer = GuidedFilter(3, 1*1e-6)(guide_img, layer)\n",
    "        # レイヤーごとの結果をまとめてlayersの形に戻す (bn, ln, 1, h, w)\n",
    "        if i == 0: \n",
    "            processed_alpha_layers = processed_layer.unsqueeze(1)\n",
    "        else:\n",
    "            processed_alpha_layers = torch.cat((processed_alpha_layers, processed_layer.unsqueeze(1)), dim=1)\n",
    "    \n",
    "    return processed_alpha_layers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "target_layer_number = [0, 1] # マスクで操作するレイヤーの番号\n",
    "mask_path = 'path/to/mask.image'\n",
    "\n",
    "\n",
    "## Define functions for mask operation.\n",
    "# マスクを受け取る関数\n",
    "# target_layer_numberが冗長なレイヤーの番号（２つ）のリスト．これらのレイヤーに操作を加える\n",
    "\n",
    "def load_mask(mask_path):\n",
    "    mask = cv2.imread(mask_path, 0) #白黒で読み込み\n",
    "    mask[mask<128] = 0.\n",
    "    mask[mask >= 128] = 1.\n",
    "    # tensorに変換する\n",
    "    mask = torch.from_numpy(mask).unsqueeze(0).unsqueeze(0).float().cuda()\n",
    "    \n",
    "    return mask\n",
    "        \n",
    "\n",
    "def mask_operate(alpha_layers, target_layer_number, mask_path):\n",
    "    layer_A = alpha_layers[:, target_layer_number[0], :, :, :]\n",
    "    layer_B = alpha_layers[:, target_layer_number[1], :, :, :]\n",
    "    \n",
    "    layer_AB = layer_A + layer_B\n",
    "    mask = load_mask(mask_path)\n",
    "    \n",
    "    mask = cut_edge(mask)\n",
    "    \n",
    "    layer_A = layer_AB * mask\n",
    "    layer_B = layer_AB * (1. - mask)\n",
    "    \n",
    "    return_alpha_layers = alpha_layers.clone()\n",
    "    return_alpha_layers[:, target_layer_number[0], :, :, :] = layer_A\n",
    "    return_alpha_layers[:, target_layer_number[1], :, :, :] = layer_B\n",
    "    \n",
    "    return return_alpha_layers\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "# datasetにある画像のパスを置き換えてしまう\n",
    "test_dataset.imgs_path[0] = img_path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start!\n",
      "img # 0\n",
      "0.012901067733764648\n",
      "Saved to results/sample/apple.jpg/...\n"
     ]
    }
   ],
   "source": [
    "print('Start!')\n",
    "img_number = 0\n",
    "\n",
    "\n",
    "mean_estimation_time = 0\n",
    "with torch.no_grad():\n",
    "    for batch_idx, (target_img, primary_color_layers) in enumerate(test_loader):\n",
    "        if batch_idx != img_number:\n",
    "            print('Skip ', batch_idx)\n",
    "            continue\n",
    "        print('img #', batch_idx)\n",
    "        target_img = cut_edge(target_img)\n",
    "        target_img = target_img.to(device) # bn, 3ch, h, w\n",
    "        primary_color_layers = primary_color_layers.to(device)\n",
    "        #primary_color_layers = color_regresser(target_img)\n",
    "        ##\n",
    "        ##\n",
    "        primary_color_layers = replace_color(primary_color_layers, manual_colors) #ここ\n",
    "        ##\n",
    "        #print(primary_color_layers.mean())\n",
    "        #print(primary_color_layers.size())\n",
    "        start_time = time.time()\n",
    "        primary_color_pack = primary_color_layers.view(primary_color_layers.size(0), -1 , primary_color_layers.size(3), primary_color_layers.size(4))\n",
    "        primary_color_pack = cut_edge(primary_color_pack)\n",
    "        primary_color_layers = primary_color_pack.view(primary_color_pack.size(0),-1,3,primary_color_pack.size(2), primary_color_pack.size(3))\n",
    "        pred_alpha_layers_pack = mask_generator(target_img, primary_color_pack)\n",
    "        pred_alpha_layers = pred_alpha_layers_pack.view(target_img.size(0), -1, 1, target_img.size(2), target_img.size(3))\n",
    "        ## Alpha Layer Proccessing\n",
    "        processed_alpha_layers = alpha_normalize(pred_alpha_layers) \n",
    "        #processed_alpha_layers = mask_operate(processed_alpha_layers, target_layer_number, mask_path) # Option\n",
    "        processed_alpha_layers = proc_guidedfilter(processed_alpha_layers, target_img) # Option\n",
    "        processed_alpha_layers = alpha_normalize(processed_alpha_layers)  # Option\n",
    "        ##\n",
    "        mono_color_layers = torch.cat((primary_color_layers, processed_alpha_layers), 2) #shape: bn, ln, 4, h, w\n",
    "        mono_color_layers_pack = mono_color_layers.view(target_img.size(0), -1 , target_img.size(2), target_img.size(3))\n",
    "        residue_pack  = residue_predictor(target_img, mono_color_layers_pack)\n",
    "        residue = residue_pack.view(target_img.size(0), -1, 3, target_img.size(2), target_img.size(3))\n",
    "        pred_unmixed_rgb_layers = torch.clamp((primary_color_layers + residue), min=0., max=1.0)\n",
    "        reconst_img = (pred_unmixed_rgb_layers * processed_alpha_layers).sum(dim=1)\n",
    "        end_time = time.time()\n",
    "        estimation_time = end_time - start_time\n",
    "        print(estimation_time)\n",
    "        mean_estimation_time += estimation_time\n",
    "        \n",
    "        if True:\n",
    "            # batchsizeは１で計算されているはず．それぞれ保存する．\n",
    "            save_layer_number = 0\n",
    "            save_image(primary_color_layers[save_layer_number,:,:,:,:],\n",
    "                   'results/%s/%s/test' % (run_name, img_name) + '_img-%02d_primary_color_layers.png' % batch_idx)\n",
    "            save_image(reconst_img[save_layer_number,:,:,:].unsqueeze(0),\n",
    "                   'results/%s/%s/test' % (run_name, img_name)  + '_img-%02d_reconst_img.png' % batch_idx)\n",
    "            save_image(target_img[save_layer_number,:,:,:].unsqueeze(0),\n",
    "                   'results/%s/%s/test' % (run_name, img_name)  + '_img-%02d_target_img.png' % batch_idx)\n",
    "\n",
    "            # RGBAの４chのpngとして保存する\n",
    "            RGBA_layers = torch.cat((pred_unmixed_rgb_layers, processed_alpha_layers), dim=2) # out: bn, ln, 4, h, w\n",
    "            # test ではバッチサイズが１なので，bn部分をなくす\n",
    "            RGBA_layers = RGBA_layers[0] # ln, 4. h, w\n",
    "            # ln ごとに結果を保存する\n",
    "            for i in range(len(RGBA_layers)):\n",
    "                save_image(RGBA_layers[i, :, :, :], 'results/%s/%s/img-%02d_layer-%02d.png' % (run_name, img_name, batch_idx, i) )\n",
    "            print('Saved to results/%s/%s/...' % (run_name, img_name))\n",
    "            \n",
    "        if False:\n",
    "            ### mono_colorの分も保存する ###\n",
    "            # RGBAの４chのpngとして保存する\n",
    "            mono_RGBA_layers = torch.cat((primary_color_layers, processed_alpha_layers), dim=2) # out: bn, ln, 4, h, w\n",
    "            # test ではバッチサイズが１なので，bn部分をなくす\n",
    "            mono_RGBA_layers = mono_RGBA_layers[0] # ln, 4. h, w\n",
    "            # ln ごとに結果を保存する\n",
    "            for i in range(len(mono_RGBA_layers)):\n",
    "                save_image(mono_RGBA_layers[i, :, :, :], 'results/%s/%s/mono_img-%02d_layer-%02d.png' % (run_name, img_name, batch_idx, i) )\n",
    "\n",
    "            save_image((primary_color_layers * processed_alpha_layers).sum(dim=1)[save_layer_number,:,:,:].unsqueeze(0),\n",
    "                   'results/%s/%s/test' % (run_name, img_name)  + '_mono_img-%02d_reconst_img.png' % batch_idx)   \n",
    "        \n",
    "        \n",
    "        if batch_idx == 0:\n",
    "            break # debug用\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(Appendix) Save alpha channel and RGB channel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 処理まえのアルファを保存\n",
    "for i in range(len(pred_alpha_layers[0])):\n",
    "            save_image(pred_alpha_layers[0,i, :, :, :], 'results/%s/%s/pred-alpha-00_layer-%02d.png' % (run_name, img_name, i) )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 処理後のアルファの保存 processed_alpha_layers\n",
    "for i in range(len(processed_alpha_layers[0])):\n",
    "            save_image(processed_alpha_layers[0,i, :, :, :], 'results/%s/%s/proc-alpha-00_layer-%02d.png' % (run_name, img_name, i) )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# 処理後のRGBの保存\n",
    "for i in range(len(pred_unmixed_rgb_layers[0])):\n",
    "    save_image(pred_unmixed_rgb_layers[0,i, :, :, :], 'results/%s/%s/rgb-00_layer-%02d.png' % (run_name, img_name, i) )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(Appendix) K-means for culculating pallete colors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 79.84641223  81.18949441  59.80645161 112.21863764 117.44504907\n",
      " 105.16519289 137.03312969  92.77037699  41.32657825 168.94288844\n",
      " 172.29588484 156.20779221  42.18568881  53.64712439  49.72246991\n",
      " 168.96553705 130.15077542  40.03661689 114.25800207  60.95682757\n",
      "  31.94237223]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import cv2\n",
    "from sklearn.cluster import KMeans\n",
    "import pandas as pd\n",
    "\n",
    "### User inputs\n",
    "num_clusters = 7\n",
    "img_name = 'apple.jpg'\n",
    "img_path = '../dataset/test/' + img_name\n",
    "\n",
    "###\n",
    "\n",
    "img = cv2.imread(img_path)[:, :, [2, 1, 0]]\n",
    "size = img.shape[:2]\n",
    "vec_img = img.reshape(-1, 3)\n",
    "model = KMeans(n_clusters=num_clusters, n_jobs=-1)\n",
    "pred = model.fit_predict(vec_img)\n",
    "pred_img = np.tile(pred.reshape(*size,1), (1,1,3))\n",
    "\n",
    "center = model.cluster_centers_.reshape(-1)\n",
    "print(center)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "img_name = 'orange.png'; manual_color_0 = [79, 81, 59]; manual_color_1 = [112, 117, 105]; manual_color_2 = [137, 92, 41]; manual_color_3 = [168, 172, 156]; manual_color_4 = [42, 53, 49]; manual_color_5 = [168, 130, 40]; manual_color_6 = [114, 60, 31]; "
     ]
    }
   ],
   "source": [
    "# Reshape for an input\n",
    "print('img_name = \\'%s\\';' % img_name, end=\" \")\n",
    "for k, i in enumerate(model.cluster_centers_):\n",
    "    print('manual_color_%d = [' % k + str(i[0].astype('int')) +', '+ str(i[1].astype('int'))+  ', '+ str(i[2].astype('int')) + '];', end=\" \")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
